﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Data;
using System.Linq;
using System.Threading;
using System.Security.Cryptography;

namespace DupeFileChecker
{
    public partial class Mainform : Form
    {
        //Dirty code is dirty. But it works :9 Should've called the program 'mudhasher' instead :u
        //I hate i couldn't use if(something == null) instead of try. Means way more slowdown. And C# isn't exactly the fastest language out there..
        public List<FileInfo> filelist = new List<FileInfo>();
        public Dictionary<string,string> filehashes = new Dictionary<string,string>();
        DateTime starttime;
        public Mainform()
        {
            InitializeComponent();
            CheckForIllegalCrossThreadCalls = false;
            treeview_dupeslist.NodeMouseDoubleClick += new TreeNodeMouseClickEventHandler(treeview_dupeslist_NodeMouseDoubleClick);
        }

        void treeview_dupeslist_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            System.Diagnostics.Process openInExplorer = new System.Diagnostics.Process();
            openInExplorer.StartInfo = new System.Diagnostics.ProcessStartInfo() { UseShellExecute = true, FileName = "explorer", Arguments = "/select," + e.Node.Text.Remove(e.Node.Text.IndexOf("  |  ")) };
            openInExplorer.Start();
        }

        private void browse_button_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog ofd = new FolderBrowserDialog();
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                textbox_directory.Text = ofd.SelectedPath;
            }
        }

        #region supporting code

        bool isvaliddir(string path)
        {
            return Directory.Exists(path);
        }

        protected string GetMD5HashFromFile(string fileName)
        {
            FileStream file = new FileStream(fileName, FileMode.Open);
            MD5 md5 = new MD5CryptoServiceProvider();
            byte[] retVal = md5.ComputeHash(file);
            file.Close();
            
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < retVal.Length; i++)
            {
                sb.Append(retVal[i].ToString("x2"));
                
            }
            return sb.ToString();
        }


        #endregion

        private void Getfiles_button_Click(object sender, EventArgs e)
        {
            starttime = DateTime.Now;
            if(!isvaliddir(textbox_directory.Text)) {return;}
            ThreadPool.QueueUserWorkItem(new WaitCallback(getfiles),textbox_directory.Text);
            
        }
        
        public void getfiles(object path)
        {
            panel_buttons.Enabled = false;

            label_busy.Text = "Getting files...";
            filesskipped = 0;
            filelist.Clear();
            getmoarfiles((string)path);
            label_files.Text = "Files: " + filelist.Count.ToString();

            label_busy.Text = "Getting hashes....";
            gethashes();
            label_equalsizefiles.Text = "Equally sized files: " + filehashes.Count;
            label_skippedfiles.Text = "Skipped: " + filesskipped.ToString();
            label_FilesRemovedDuringRuntime.Text = "Files removed during runtime: " + filesremovedduringruntime.ToString();
            label_busy.Text = "Done!";
            checkfordupes(new object());
            panel_buttons.Enabled = true;
            label_totaltime.Text = "Total Time used:\n" + (DateTime.Now - starttime).TotalSeconds.ToString() + " Seconds";
        }

        int filesskipped = 0;
        int filesremovedduringruntime = 0;
        public void gethashes()
        {
            panel_buttons.Enabled = false;
            filehashes = new Dictionary<string, string>();
            filesremovedduringruntime = 0;

            for (int j = 0; j < filelist.Count; j++)
            {
                FileInfo file = filelist[j];

                    if (file.Exists && file.Directory.Exists)
                    {
                        try
                        {
                            
                                string test;
                                if (!filehashes.TryGetValue(file.FullName, out test))
                                {

                                    filehashes.Add(file.FullName, GetMD5HashFromFile(file.FullName));
                                }

                                else
                                {
                                    //break; //File is already in there. Skip searching. Boosts performance too :D
                                }
                            
                        }
                        catch (UnauthorizedAccessException)
                        {
                            NoAccessToFile++;
                        }
                        catch (Exception)
                        {
                            //Catch any leftover errors
                            filesskipped++;
                        }
                    }
                    else
                    {
                        filesremovedduringruntime++;

                        
                        
                        label_busy.Text = "Someone REMOVED a FILE. That makes me sad :( || Getting hashes... [" + (j) + "/" + filelist.Count + "]";
                    }






                //for (int i = 0; i < filelist.Count; i++)
                //{
                //    //FileInfo item = filelist[i];


                    
                //}


                if ((j % 100) == 0)
                {
                    label_equalsizefiles.Text = "Equally sized files: " + filehashes.Count;
                    label_noaccess.Text = "Access denied on files: " + NoAccessToFile.ToString();
                    label_busy.Text = "Getting hashes... [" + (j) + "/" + filelist.Count + "]";
                    label_skippedfiles.Text = "Skipped: " + filesskipped.ToString();
                    label_FilesRemovedDuringRuntime.Text = "Files removed during runtime: " + filesremovedduringruntime.ToString();
                    label_totaltime.Text = "Time took:\n" + (DateTime.Now - starttime).TotalSeconds.ToString() + " Seconds";
                }
            }

            


            panel_buttons.Enabled = true;
        }

        int NoAccessToFile = 0;
        public void getmoarfiles(string path)
        {

            try
            {
                Directory.GetFiles(path)
                    .ToList()
                    .ForEach(s => filelist.Add(new FileInfo(s)));

                Directory.GetDirectories(path)
                    .ToList()
                    .ForEach(s => getmoarfiles(s));
            }
            catch (UnauthorizedAccessException)
            {
                // ok, so we are not allowed to dig into that directory. Move on.
                //Wonder if this causes whole directories to be skipped when a file cause a problem.. hope not o3o
                NoAccessToFile++;
            }
            catch (Exception)
            {
                filesskipped++;
                
            }
            if ((filelist.Count % 100) == 0)
            {
                label_files.Text = "Files: " + filelist.Count.ToString();
                label_skippedfiles.Text = "Skipped: " + filesskipped.ToString();
                label_noaccess.Text = "No Access to files: " + NoAccessToFile.ToString();
                label_totaltime.Text = "Time took:\n" + (DateTime.Now - starttime).TotalSeconds.ToString() + " Seconds";
            }

        }

        
        List<string> fileswithduplicate = new List<string>();

        public void checkfordupes(object blargh)
        {
            panel_buttons.Enabled = false;
            label_busy.Text = "Finding duplicate files...";
            int iteration = 0;
            foreach (string file in filehashes.Values)
            {
                if (filehashes.Values.ToList().Count(s => s == file) > 1 && !(fileswithduplicate.Contains(file)))
                {
                    fileswithduplicate.Add(file);
                    label_duplicates.Text = "Duplicates: " + fileswithduplicate.Count;
                }
                iteration++;
                if ((iteration % 100) == 0)
                {
                     label_busy.Text = "Finding duplicate files...[" + iteration + "/" + filehashes.Count + "]";
                }
               
            }
            
            getdupenodes(new object());
            panel_buttons.Enabled = true;
            label_busy.Text = "Done!";
            
        }

        List<filedupe> dupeslist = new List<filedupe>();

        void getdupenodes(object blargh)
        {
            dupeslist.Clear();
            treeview_dupeslist.Nodes.Clear();
            foreach (string value in fileswithduplicate)
            {
                foreach (KeyValuePair<string, string> item in filehashes)
                {
                    if (item.Value == value)
                    {
                        List<string> hashesindupeslist = new List<string>();
                        foreach (filedupe curhash in dupeslist)
                        {
                            hashesindupeslist.Add(curhash.hash);
                        }
                        if (hashesindupeslist.Contains(value))
                        {
                            filedupe doop = new filedupe();
                            doop.dupes.Add(new filedupe());
                            doop.filename = item.Key;
                            doop.hash = item.Value;
                            dupeslist[hashesindupeslist.FindIndex(s => s == value)].dupes.Add(doop);
                        }
                        else
                        {
                            filedupe doop = new filedupe();
                            //doop.dupes.Add(new filedupe());
                            doop.filename = item.Key;
                            doop.hash = item.Value;
                            dupeslist.Add(doop);
                        }

                    }
                }
                
            }

            foreach (filedupe doop in dupeslist)
            {
                TreeNode node = new TreeNode(doop.filename + "  |  " + doop.hash);
                foreach (filedupe dupe in doop.dupes)
                {
                    node.Nodes.Add(dupe.filename + "  |  " + doop.hash);
                }
                if (treeview_dupeslist.InvokeRequired)
                {
                     addnode addnoteitem1 = new addnode(addnodeitem);
                     curnode = node;
                    treeview_dupeslist.Invoke(addnoteitem1);
                }
                else
                {
                    treeview_dupeslist.Nodes.Add(node);
                }
                
            }
            panel_buttons.Enabled = true;
        }
       
        TreeNode curnode;
        delegate void addnode();
        public  void addnodeitem()
        {
            TreeNode nawd = curnode;
            treeview_dupeslist.Nodes.Add(nawd);
        }

        class filedupe
        {
            public string filename;
            public string hash;
            public List<filedupe> dupes = new List<filedupe>();
        }

        private void button_deletedupes_Click(object sender, EventArgs e)
        {
            ThreadPool.QueueUserWorkItem(new WaitCallback(deletefiles));
        }
        void deletefiles(object blargh)
        {
            panel_buttons.Enabled = false;
            label_busy.Text = "Deleting files, standby..";
            if (MessageBox.Show("Because it's going to delete ALL the duplicate versions of a file after the first one it finds!\nSometimes, duplicate files are NEEDED.\n\nI am NOT in ANY WAY responsible for you accidentally-the-whole'ing your damn files.", "ARE YOU THOROUGHLY FUCKING SURE.", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == System.Windows.Forms.DialogResult.Yes)
            {
                foreach (var fileNOTtodelete in dupeslist)
                {
                    foreach (var filetodelete in fileNOTtodelete.dupes)
                    {
                        try
                        {
                            File.Delete(filetodelete.filename);
                        }
                        catch (Exception)
                        {
                            filesremovedduringruntime++;
                            label_FilesRemovedDuringRuntime.Text = "Files removed during runtime: " + filesremovedduringruntime.ToString();
                        }
                        
                    }
                }
                treeview_dupeslist.Nodes.Clear();
                dupeslist.Clear();
                filelist.Clear();
                MessageBox.Show("Bam. Done.");
            }
            label_busy.Text = "Done!";
            panel_buttons.Enabled = true;
        }
        

        private void button_save_filelist_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                StringBuilder sbuilder = new StringBuilder();
                foreach (var Originalfile in dupeslist)
                {
                    sbuilder.Append("Original: Filename: " + Originalfile.filename + "   |   Hash: " + Originalfile.hash + "\r\n");
                    sbuilder.Append("\t\\\r\n");
                    foreach (var DuplicateFile in Originalfile.dupes)
                    {
                        sbuilder.Append("\tDuplicate: Filename: " + DuplicateFile.filename + "   |   Hash: " + DuplicateFile.hash + "\r\n");
                    }
                    sbuilder.Append("\r\n");
                }
                File.WriteAllText(sfd.FileName, sbuilder.ToString());
                MessageBox.Show("File Saved to " + sfd.FileName, "Succes!", MessageBoxButtons.OK, MessageBoxIcon.Information);
            }


        }

    

 
    }
}
